package org.tcp;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.BindException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

/**
 * 基于tcp的回显服务器
 */
public class TCPEchoServer {
    // 声明用于服务器端的类
    protected ServerSocket socket;
    /**
     * 构造方法
     * @param port 服务端的端口号
     */
    public TCPEchoServer(int port) throws IOException {
        if(port > 65535 || port < 1024) {
            throw new BindException("端口必须在1025到65535之间");
        }
        // 根据指定的端口号初始化Socket服务
        this.socket = new ServerSocket(port);
    }
    /**
     * 开始处理客户端的连接请求
     */
    public void start() throws IOException{
        System.out.println("服务已启动，等待客户端连接.");
        // 循环处理连接请求
        while(true) {
            // 接收客户端请求, 如果没有客户端连接就阻塞等待
            Socket clientSocket = socket.accept();
            // 接收到客户端连接之后，交给专门的方法进行数据处理
            processConnections (clientSocket);
        }
    }

    // 为了方便维护，专门把数据处理的逻辑抽取成一个方法
    protected void processConnections(Socket clientSocket) {
// 打印连接日志
        System.out.printf("[%s : %d 客户端上线了.\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());
        // 所有的通信数据都封装在Socket对象中，对应的就是入参clientSocket，TCP传输形式时字节流
        // 1. 通过输入输出流来获取和发送数据
        try(InputStream inputStream = clientSocket.getInputStream();
        OutputStream outputStream = clientSocket.getOutputStream()) {
            // 2. 循环读取客户端发来的数据
            while(true) {
                //通过Scanner简化文本请求的读取过程
                Scanner scanner = new Scanner(inputStream);
                // 3. 判断是否还有数据
                if(!scanner.hasNextLine()){
                    System.out.printf("[%s : %d] 客户端下线了。\n",clientSocket.getInetAddress().toString(),clientSocket.getPort());
                    break;
                }
                //4.获取用户发来的具体内容
                String request = scanner.nextLine();
                //5.根据请求进行响应
                String response = process(request);
                // 6. 把响应写入输出流
                // 6.1 通过PrintWriter来简化写入操作
                PrintWriter writer = new PrintWriter(outputStream);
                // 6.2 向输出流中写入响应
                writer.println(response);
                // writer.write(response); // 调用writer有没有问题？有问题
                // 7. 强制刷新缓冲区，相当于系统把数据交给了网卡，也相当于程序把数据发送出去了
                writer.flush();

                // 8. 打印日志
                System.out.printf("[%s:%d] request = %s, response = %s\n", clientSocket.getInetAddress().toString(),
                        clientSocket.getPort(), request, response);
            }

        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    //处理响应的方式
    protected String process(String request){
        return request;
    }

    public static void main(String[] args) throws IOException {
        TCPEchoServer server = new TCPEchoServer(9090);
       server.start();
    }
}

